/****************************************************************************************
**  File: Motion.hpp
**  Author: Asteroth
**  Date: 03/01/2010
****************************************************************************************/

#ifndef __ZEN_MOTION__
#define __ZEN_MOTION__

#include <glm/glm.hpp>

namespace zen {
namespace math {

template <class T>
class Motionx {
private:
    glm::detail::tmat4x4<T> mat;

public:
	Motionx(): mat() {}
    Motionx(const Motionx<T> &copy): mat(copy.mat) {}

	inline glm::detail::tmat4x4<T>& matrix() const {return mat;}
	inline glm::detail::tmat4x4<T>& matrix() {return mat;}
	inline glm::detail::tmat4x4<T> lookAt() const {return glm::lookAt(position(), position() + axisY(), axisZ());}

    /// returns the local axis
	inline glm::detail::tvec3<T> axisX() const {return glm::normalize(glm::detail::tvec3<T>(mat[0]));}
	inline glm::detail::tvec3<T> axisY() const {return glm::normalize(glm::detail::tvec3<T>(mat[1]));}
	inline glm::detail::tvec3<T> axisZ() const {return glm::normalize(glm::detail::tvec3<T>(mat[2]));}

    /// returns the global position.
    inline T positionX() const {return mat[3][0];}
    inline T positionY() const {return mat[3][1];}
    inline T positionZ() const {return mat[3][2];}
    inline glm::detail::tvec3<T> position() const {return glm::detail::tvec3<T>(mat[3]);}

    /// repositions along global axis.
    inline void positionX(T x) {mat[3][0] = x;}
    inline void positionY(T y) {mat[3][1] = y;}
    inline void positionZ(T z) {mat[3][2] = z;}
    inline void position(T x, T y, T z) {mat[3][0] = x; mat[3][1] = y; mat[3][2] = z;}
    inline void position(const glm::detail::tvec3<T> &v) {position(v.x, v.y, v.z);}

    /// translates along global axis, relative to current position.
    inline void translateX(T x) {mat[3][0] += x;}
    inline void translateY(T y) {mat[3][1] += y;}
    inline void translateZ(T z) {mat[3][2] += z;}
    inline void translate(T x, T y, T z) {mat[3][0] += x; mat[3][1] += y; mat[3][2] += z;}
    inline void translate(const glm::detail::tvec3<T> &v) {translate(v.x, v.y, v.z);}

    /// moves along the local axis, relative to current position.
    inline void moveX(T x) {mat[3][0] += mat[0][0] * x; mat[3][1] += mat[0][1] * x; mat[3][2] += mat[0][2] * x;}
    inline void moveY(T y) {mat[3][0] += mat[1][0] * y; mat[3][1] += mat[1][1] * y; mat[3][2] += mat[1][2] * y;}
    inline void moveZ(T z) {mat[3][0] += mat[2][0] * z; mat[3][1] += mat[2][1] * z; mat[3][2] += mat[2][2] * z;}
    inline void move(T x, T y, T z) {mat[3][0] += mat[0][0] * x + mat[1][0] * x + mat[2][0] * z; mat[3][1] += mat[0][1] * x + mat[1][1] * x + mat[2][1] * z; mat[3][2] += mat[0][2] * x + mat[1][2] * x + mat[2][2] * z;}
    inline void move(const glm::detail::tvec3<T> &v) {move(v.x, v.y, v.z);}

    /// rotates around the global axis.
    inline void rotateX(double angle) {turn(angle, glm::detail::tvec3<T>(1.0, 0.0, 0.0));}
    inline void rotateY(double angle) {turn(angle, glm::detail::tvec3<T>(0.0, 1.0, 0.0));}
    inline void rotateZ(double angle) {turn(angle, glm::detail::tvec3<T>(0.0, 0.0, 1.0));}

    /// turns around a local axis.
    inline void turnX(double angle) {turn(angle, axisX());}
    inline void turnY(double angle) {turn(angle, axisY());}
    inline void turnZ(double angle) {turn(angle, axisZ());}

    /// turns around a custom axis.
    inline void turn(double angle, const glm::detail::tvec3<T> &axis) {
        glm::detail::tmat3x3<T> m(glm::rotate((T)angle, axis));
        glm::detail::tvec3<T> v;
        v = glm::normalize(m * glm::detail::tvec3<T>(mat[0][0], mat[0][1], mat[0][2]));
        mat[0][0] = v.x; mat[0][1] = v.y; mat[0][2] = v.z;
        v = glm::normalize(m * glm::detail::tvec3<T>(mat[1][0], mat[1][1], mat[1][2]));
        mat[1][0] = v.x; mat[1][1] = v.y; mat[1][2] = v.z;
        v = glm::normalize(m * glm::detail::tvec3<T>(mat[2][0], mat[2][1], mat[2][2]));
        mat[2][0] = v.x; mat[2][1] = v.y; mat[2][2] = v.z;
    }

	inline void lookAt(T posX, T posY, T posZ, T targetX, T targetY, T targetZ, T upX, T upY, T upZ) {
		lookAt(glm::detail::tvec3<T>(posX, posY, posZ), glm::detail::tvec3<T>(targetX, targetY, targetZ), glm::detail::tvec3<T>(upX, upY, upZ));
	}
	inline void lookAt(const glm::detail::tvec3<T> &position, const glm::detail::tvec3<T> &target, const glm::detail::tvec3<T> &up) {
		glm::detail::tvec3<T> y = glm::normalize(target - position);
		glm::detail::tvec3<T> z = glm::normalize(up);
		glm::detail::tvec3<T> x = glm::normalize(glm::cross(y, z));
		z = glm::cross(x, y);
		mat[0][0] = x.x;
		mat[0][1] = x.y;
		mat[0][2] = x.z;
		mat[0][3] = T(0.0);
		mat[1][0] = y.x;
		mat[1][1] = y.y;
		mat[1][2] = y.z;
		mat[1][3] = T(0.0);
		mat[2][0] = z.x;
		mat[2][1] = z.y;
		mat[2][2] = z.z;
		mat[2][3] = T(0.0);
		mat[3][0] = position.x;
		mat[3][1] = position.y;
		mat[3][2] = position.z;
		mat[3][3] = T(1.0);
	}
};


typedef Motionx<float> Motion;
typedef Motionx<double> Motiond;

} // namespace math
} // namespace zen

#endif
